﻿/* 
 *  <copyright file="Slicer.cs" company="Wesley Jowitt">
 *  
 *  SobrietyEngine: Copyright (c) 2011 Wesley Jowitt
 *  
 *  This software is distributed under a software license.
 *   * As of the creation time of this file, the license in use is the Microsoft Permissive License (Ms-PL) 
 *    * The license can be viewed at:
 *     * License.Txt in the root folder of this project
 *      * http://sobrietyengine.codeplex.com/license
 *       * http://www.microsoft.com/opensource/licenses.mspx
 *       
 *  </copyright>
 *   */

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SobrietyEngine.Geometry
{
    public static class Slicer
    {
        /// <summary>
        /// Creates a list of line segments from M that intersect P. M must be processedby the ModelverticieProcessor
        /// which creates a list of triangles in the model and attaches them to the Tag property
        /// unsure of the accuracy of this method. seems to get everything, but creates WAY too many segments
        /// </summary>
        /// <param name="m">The m.</param>
        /// <param name="p">The p.</param>
        /// <returns></returns>
        static public List<LineSegment> Slice(Model m, Plane p)
        {
            if (m.Tag == null)
                throw new Exception("Model needs to be processed using Modelverticie processor");

            Vector3[] vertList = (Vector3[])((Dictionary<String, object>)m.Tag)["Vertices"];
            Vector3[] tri = new Vector3[3];
            Boolean coincident = false;            
            Boolean found = false;
            Vector3 intersection = new Vector3();
            List<LineSegment> segments = new List<LineSegment>();
            
            for (int ind = 0; ind < vertList.Length; ind += 3)
            {
                LineSegment seg = new LineSegment();
                int vertsFound = 0;

                tri[0] = vertList[ind];
                tri[1] = vertList[ind + 1];
                tri[2] = vertList[ind + 2];

                //if we find just one vertex intersection (lookAt of triangle), ignore it.
                //if we are coincident, add line
                found = Intersect.SegmentPlane(ref tri[0],ref tri[1],ref p,out coincident,out intersection);
                if (coincident)
                    segments.Add(new LineSegment(tri[0], tri[1]));
                else if (found)
                {
                    seg.points[vertsFound] = intersection;
                    vertsFound++;
                }


                found = Intersect.SegmentPlane(ref tri[1], ref tri[2], ref p, out coincident, out intersection);
                if (coincident)
                    segments.Add(new LineSegment(tri[1], tri[2]));
                else if (found)
                {
                    seg.points[vertsFound] = intersection;
                    vertsFound++;
                }

                found = Intersect.SegmentPlane(ref tri[2], ref tri[0], ref p, out coincident, out intersection);
                if (coincident)
                    segments.Add(new LineSegment(tri[2], tri[0]));
                else if (found)
                {
                    if (vertsFound < 2)
                        seg.points[vertsFound] = intersection;
                    else
                        System.Diagnostics.Debug.Assert(false, "Found 3 linesegs of tri-face intersecting plane in slicer without coincidence. Something isnt working right, but ignorable");
                    vertsFound++;
                }

                
                if (vertsFound == 2)
                    segments.Add(seg);

            }
            
            //sort segments into contours
            return segments;
        }

        static public List<LineSegment> Slice(ModelMeshPart m, Plane p)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// checks if line end points meet. used in Merge. not sure if this works correctly.
        /// </summary>
        /// <param name="s0">The s0.</param>
        /// <param name="s1">The s1.</param>
        /// <param name="tol">The tol.</param>
        /// <param name="seg">The seg.</param>
        /// <returns></returns>
        static public Boolean LinesEndpointsMeet(LineSegment s0, LineSegment s1, float tol, out LineSegment seg)
        {
            List<KeyValuePair<int, int>> matches = new List<KeyValuePair<int, int>>();
            //points that make a segment from farthest points
            LineSegment farthest = new LineSegment();
            float maxDist = 0;

            for (int a = 0; a < 2; ++a)
            {
                for (int b = 0; b < 2; b++)
                {
                    if(Vector3.DistanceSquared(s0.points[a], s1.points[b]) > maxDist)
                    {
                        maxDist = Vector3.DistanceSquared(s0.points[a], s1.points[b]);
                        farthest.points[0] = s0.points[a];
                        farthest.points[1] = s1.points[b];
                    }
                    if (Intersect.PointPoint(s0.points[a], s1.points[b], tol))
                    {
                        matches.Add(new KeyValuePair<int,int>(a,b));
                    }
                }
            }

            //matches on two points
            if (matches.Count == 2)
            {
                seg = farthest;
                return true;
            }

            //matches on one lookAt
            if (matches.Count == 1)
            {
                seg = farthest;
                return true;
            }

            if (matches.Count > 2)
            {
                //segments are points that are equal. merge em, i guess, doesnt matter how
                seg = farthest;
                return true;
            }

            seg = null;
            return false;
        }


        /// <summary>
        /// Removes the short edges that are shorter then distTOl.
        /// </summary>
        /// <param name="segs">The segs.</param>
        /// <param name="distTol">The dist tol.</param>
        /// <returns></returns>
        static public List<LineSegment> RemoveShortEdges(List<LineSegment> segs, float distTol)
        {
            List<LineSegment> keep = new List<LineSegment>();
            foreach (LineSegment s in segs)
            {
                float dist = Vector3.DistanceSquared(s.points[0], s.points[1]);
                if (dist < distTol * distTol)
                    continue;
                keep.Add(s);
            }
            return keep;
        }

        /// <summary>
        /// Merges the segments. Tries to combine them into larger segments using tolerances
        /// unknown how well it works. Seems to help with some of thelarger meshes with too many face
        /// but ruins geometry in some of the simple ones. 
        /// </summary>
        /// <param name="segs">The segs.</param>
        /// <param name="dotTol">The dot tol.</param>
        /// <param name="distTol">The dist tol.</param>
        /// <param name="smallEdgeTol">The small edge tol.</param>
        /// <returns></returns>
        static public List<LineSegment> MergeSegments(List<LineSegment> segs, float dotTol, float distTol)
        {
            List<LineSegment> merged = new List<LineSegment>();

            //get slope of each segment
            LineSegment s0, s1;
            Vector3 fDiff, sDiff;
            int mergedCount = 0;
            int skipped = 0;

            //clear merge flags on incoming segs
            //clear merge flags
            foreach (LineSegment seg in segs)
                seg.MergedFlag = false;

            for (int fi = 0; fi < segs.Count; ++fi)
            {
                Boolean foundMerge = false;
                fDiff = segs[fi].points[0] - segs[fi].points[1];
                s0 = segs[fi];

                //if we already merged this seg
                if (s0.MergedFlag)
                {
                    ++skipped;
                    continue;
                }

                for (int si = fi + 1; si < segs.Count; ++si)
                {                    
                    sDiff = segs[si].points[0] - segs[si].points[1];
                    s1 = segs[si];

                    //if we already merged this seg
                    if (s1.MergedFlag)
                    {
                        ++skipped;
                        continue;
                    }
                    //check if they are near each other
                    LineSegment mergedSegs = null;
                    Boolean touch = LinesEndpointsMeet(s0, s1, distTol, out mergedSegs);

                    if (!touch)
                        continue;

                    //check direction
                    fDiff.Normalize();
                    sDiff.Normalize();
                    float dot = Vector3.Dot(fDiff, sDiff);
                    if (Math.Abs(dot) > Math.Abs(dotTol))
                    {
                        //merge
                        foundMerge = true;
                        merged.Add(mergedSegs);
                        ++mergedCount;
                        s1.MergedFlag = true;
                        s0.MergedFlag = true;
                    }
                }

                //we need to retain this edge.
                if (foundMerge == false)
                    merged.Add(s0);
            }

            //clear merge flags
            foreach (LineSegment seg in merged)
                seg.MergedFlag = false;

            return merged;
        }
    }
}
